﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Raona.Code.Model;
using EnvDTE;
using EnvDTE80;
using Raona.Code.Contracts.EventArguments;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Threading;
using Raona.Code.Contracts.Inspection;

namespace Raona.Code.Inspection
{
    /// <summary>
    /// Parses the files in a project looking for WCF OperationContract's
    /// </summary>
    public class OperationContractInspector : IOperationContractInspector
    {
        #region Instance Fields (Private)

        /// <summary>
        /// The project to inspect
        /// </summary>
        Project contractsProject = null;
        // The code inspection result is a collection of namespaces
        // containing the interfaces found
        private Dictionary<string, Namespace> namespaces = new Dictionary<string, Namespace>();
        // The client base class
        private string clientBaseName = null;
        /// <summary>
        /// The namespace for the generated service contracts
        /// </summary>
        private string serviceContractsNamespace = null;

        private BackgroundWorker projectInterfacesWorker = null;

        private SynchronizationContext synchronizationContext = null;

        #endregion

        #region Events (Public)

        /// <summary>
        /// Event fired on each process completion step
        /// </summary>
        public event EventHandler<ProgressEventArgs> ProgressChanged;
        /// <summary>
        /// Event fired when the process finishes
        /// </summary>
        public event EventHandler<ProgressResultEventArgs> ProcessFinished;

        #endregion

        #region Instance Constructors (Public)

        public OperationContractInspector(Project contractsProject)
        {
            this.contractsProject = contractsProject;
            this.synchronizationContext = SynchronizationContext.Current;
        }

        #endregion

        #region Instance Properties (Public)

        /// <summary>
        /// The collection of namespaces resulting of the inspection process
        /// </summary>
        public IEnumerable<Namespace> Namespaces
        {
            get
            {
                return this.namespaces.Values.AsEnumerable();
            }
        }

        #endregion

        #region Instance Methods (Public)

        public Dictionary<string, Namespace> GetInterfaces(string clientBase, string serviceContractsNamespace)
        {
            this.GetProjectInterfaces(this.contractsProject, clientBase, serviceContractsNamespace);
            return this.namespaces;
        }

        public void GetInterfacesAsync(string clientBase, string serviceContractsNamespace)
        {
            this.projectInterfacesWorker = new BackgroundWorker();
            this.projectInterfacesWorker.WorkerReportsProgress = true;
            this.projectInterfacesWorker.WorkerSupportsCancellation = true;
            this.projectInterfacesWorker.DoWork += new DoWorkEventHandler(this.OnProjectInterfacesWorkerDoWork);
            this.projectInterfacesWorker.ProgressChanged += new ProgressChangedEventHandler(this.OnProjectInterfacesWorkerProgressChanged);

            this.projectInterfacesWorker.RunWorkerAsync(new Tuple<Project, string, string>(this.contractsProject, clientBase, serviceContractsNamespace));
        }

        public void GetProjectInterfaces(Project project, string clientBase, string serviceContractsNamespace)
        {
            this.namespaces.Clear();
            this.clientBaseName = string.IsNullOrEmpty(clientBase) ? "ClientBase" : clientBase;
            this.serviceContractsNamespace = string.IsNullOrEmpty(serviceContractsNamespace) ? "http://tempuri.org/" : serviceContractsNamespace;
            try
            {
                foreach (ProjectItem projectItem in project.ProjectItems)
                {
                    if (this.IsProjectInterfacesCancellationPending)
                        break;

                    this.GetProjectInterfaces(projectItem, clientBase);
                }

                this.ReportResult();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
                this.ReportResult(ex);
            }
            
            if (this.projectInterfacesWorker != null)
            {
                this.projectInterfacesWorker.Dispose();
                this.projectInterfacesWorker = null;
            }
        }

        public void CancelGetInterfaces()
        {
            if (this.projectInterfacesWorker != null && this.projectInterfacesWorker.IsBusy)
                this.projectInterfacesWorker.CancelAsync();
        }

        public void Dispose()
        {
            if (this.projectInterfacesWorker != null)
                this.projectInterfacesWorker.Dispose();
        }

        #endregion

        #region Instance Properties (Private)
        
        private bool IsProjectInterfacesCancellationPending
        {
            get
            {
                //return false;
                return this.projectInterfacesWorker != null && this.projectInterfacesWorker.CancellationPending;
            }
        }

        #endregion

        #region Instance Methods (Private)

        private void GetProjectInterfaces(ProjectItem projectItem, string clientBase)
        {
            if (this.IsProjectInterfacesCancellationPending)
                return;

            if (projectItem.Kind == EnvDTEConstants.vsProjectItemKindPhysicalFolder)
            {
                foreach (ProjectItem childItem in projectItem.ProjectItems)
                {
                    if (this.IsProjectInterfacesCancellationPending)
                        break;

                    this.GetProjectInterfaces(childItem, clientBase);
                }
            }
            // Process project files ending in .cs
            else if (projectItem.Kind == EnvDTEConstants.vsProjectItemKindPhysicalFile && projectItem.Name.EndsWith(".cs"))
            {
                foreach (CodeElement codeElement in projectItem.FileCodeModel.CodeElements)
                {
                    if (this.IsProjectInterfacesCancellationPending)
                        break;

                    // Process namespaces
                    if (codeElement.Kind == vsCMElement.vsCMElementNamespace)
                    {
                        Namespace ns = this.AddNamespace(codeElement.Name);
                        // Process interfaces
                        foreach (CodeElement childElement in codeElement.Children)
                        {
                            if (this.IsProjectInterfacesCancellationPending)
                                break;

                            if (childElement.Kind == vsCMElement.vsCMElementInterface)
                            {
                                Interface itf = new Interface { Name = childElement.Name, ClientBaseName = this.clientBaseName, ServiceContractNamespace = this.serviceContractsNamespace };
                                ns.Interfaces.Add(itf);

                                if (this.projectInterfacesWorker != null)
                                    this.projectInterfacesWorker.ReportProgress(0, itf);
                                else
                                    this.ReportProgress(itf);
                                
                                // Process interface methods
                                foreach (CodeElement interfaceMember in childElement.Children)
                                {
                                    if (IsProjectInterfacesCancellationPending)
                                        break;

                                    if (interfaceMember.Kind == vsCMElement.vsCMElementFunction)
                                    {
                                        CodeFunction codeFunction = (CodeFunction)interfaceMember;
                                        Method method = new Method { Name = codeFunction.Name, OverloadedName = codeFunction.Name, ReturnType = codeFunction.Type.AsString };
                                        itf.AddMethod(method);
                                        // Find the client overloaded name for the method in the OperationContractAttribute's Name property
                                        if (codeFunction.Attributes.Count > 0 &&
                                            codeFunction.Attributes.Item(1).Name == "OperationContract" &&
                                            codeFunction.Attributes.Item(1).Children.Count > 0)
                                        {
                                            string value = ((CodeAttributeArgument)codeFunction.Attributes.Item(1).Children.Item(1)).Value;
                                            // The value includes the surrounding quotes
                                            method.OverloadedName = value.Trim('\"');
                                        }
                                        // Process method parameters
                                        foreach (CodeElement parameterElement in codeFunction.Parameters)
                                        {
                                            if (this.IsProjectInterfacesCancellationPending)
                                                break;

                                            CodeParameter codeParameter = (CodeParameter)parameterElement;
                                            Parameter parameter = new Parameter { Name = codeParameter.Name, Type = codeParameter.Type.AsString };
                                            method.Parameters.Add(parameter);
                                        }
                                    }
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Adds a namespace to the collection of namespaces taking into account if it has been previously added or not
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private Namespace AddNamespace(string name)
        {
            Namespace nameSpace = null;
            if (this.namespaces.ContainsKey(name))
            {
                nameSpace = this.namespaces[name];
            }
            else
            {
                nameSpace = new Namespace { Name = name };
                this.namespaces.Add(name, nameSpace);
            }
            return nameSpace;
        }

        private void ReportProgress(Interface itf)
        {
            if (this.ProgressChanged != null)
            {
                this.ProgressChanged(this, new ProgressEventArgs { Message = string.Format("Processing interface: {0}", itf.Name) });
            }
        }

        private void ReportResult()
        {
            this.ReportResult(new ProgressResultEventArgs(this.namespaces));
        }

        private void ReportResult(Exception error)
        {
            this.ReportResult(new ProgressResultEventArgs(error));
        }

        private void ReportResult(ProgressResultEventArgs resultEventArgs)
        {
            if (this.ProcessFinished != null)
            {
                this.ProcessFinished(this, resultEventArgs);
            }
        }

        #endregion

        #region Event Handlers (Private)

        private void OnProjectInterfacesWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.ReportProgress((Interface)e.UserState);
        }

        private void OnProjectInterfacesWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            Tuple<Project, string, string> arguments = (Tuple<Project, string, string>)e.Argument;
            this.GetProjectInterfaces(arguments.Item1, arguments.Item2, arguments.Item3);
        }

        #endregion
    }
}
